package meatslope2.gameengine.scene;

import static meatslope2.constants.TileConstants.BLOCK;
import static meatslope2.constants.TileConstants.RAMP_LEFT_1;
import static meatslope2.constants.TileConstants.RAMP_LEFT_2;
import static meatslope2.constants.TileConstants.RAMP_RIGHT_1;
import static meatslope2.constants.TileConstants.RAMP_RIGHT_2;

import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import meatslope2.constants.BitmapConstants;
import meatslope2.constants.TileConstants;
import meatslope2.gameengine.gameobjects.Tile;
import meatslope2.gameengine.helpers.Utils;
import meatslope2.gameengine.primatives.Vector2;

/**
 * Stores a 2D array for tile collision detection as well as a plain array for drawing tiles
 * 
 * @author Mitchell Thelen
 *
 */
public final class TileWorld 
{
	// ===========================================================
	// Members
	// ===========================================================
	
	private final int pixelWidth;
	private final int pixelHeight;
	
	private final int tilesAcross;
	private final int tilesUp;
	
	//This is a 2D array that references a tile index for collisions
	private int[][] collisionGrid;
	
	//This is an array of tile positions and indexes for drawing
	private CachedTile[][] drawingGrid;
	
	// ===========================================================
	// Getters/Setters
	// ===========================================================
	
	public final int getPixelHeight()
	{
		return this.pixelHeight;
	}
	
	public final int getPixelWidth()
	{
		return this.pixelWidth;
	}
	
	public final int getTilesAcross()
	{
		return this.tilesAcross;
	}
	
	public final int getTilesUp()
	{
		return this.tilesUp;
	}
	
	public final int getTile(int x, int y)
	{
		if(x < 0 || x >= collisionGrid.length || y < 0 || y >= collisionGrid[x].length)
			return TileConstants.NOT_SET;
		
		return this.collisionGrid[x][y];
	}
	
	// ===========================================================
	// Constructors
	// ===========================================================
	private TileWorld(int tilesAcross, int tilesUp)
	{			
		this.tilesAcross = tilesAcross;
		this.tilesUp = tilesUp;
		
		this.pixelWidth = tilesAcross * BitmapConstants.TILE_WIDTH;
		this.pixelHeight = tilesUp * BitmapConstants.TILE_HEIGHT;
	}
	
	// ===========================================================
	// Methods
	// ===========================================================
	private final void setTiles(int[][] collisionGrid)
	{
		this.collisionGrid = collisionGrid;
		this.drawingGrid = buildDrawingGrid();
	}
	
	//TODO: draw relative to camera & don't draw outside of camera
	public final void draw(GL10 pGL) 
	{
		//Iterate left to right, bottom to top...
		for(int x = 0; x < this.drawingGrid.length; x++)
		{
			CachedTile[] bucket = this.drawingGrid[x];
			for(int y = 0; y < bucket.length; y++)
			{
				//Get the tile reference from the cached tile's index
				CachedTile cachedTile = bucket[y];
				Tile tile = TileConstants.getTile(cachedTile.tileIndex);
			
				//Update our static lookup tile's position before drawing
				tile.getPosition().set(cachedTile.position);
				tile.draw(pGL);
			}
		}
	}
	
	/**
	 * Builds a grid (buckets of tiles in the x-direction that are cached and ready to draw)
	 * based off the collision grid
	 */
	private CachedTile[][] buildDrawingGrid()
	{
		//Iterate through collision grid left to right, bottom to top...
		final CachedTile[][] newDrawingGrid = new CachedTile[tilesAcross][];
		
		for(int x = 0; x < this.tilesAcross; x++)
		{
			List<CachedTile> bucket = new ArrayList<CachedTile>();
			for(int y = 0; y < this.tilesUp; y++)
			{
				int tileIndex = this.collisionGrid[x][y];
				if(tileIndex == TileConstants.NOT_SET) //This tile hasn't been set to anything, don't bother putting it in a bucket
					continue;
				
				//Created a CachedTile to put in the drawingGrid
				Vector2 pos = new Vector2(x * BitmapConstants.TILE_WIDTH, y * BitmapConstants.TILE_HEIGHT);
				CachedTile newCachedTile = new CachedTile(tileIndex, pos);
				bucket.add(newCachedTile);
			}
			
			CachedTile[] bucketContent = Utils.toArray(bucket, new CachedTile[bucket.size()]);
			newDrawingGrid[x] = bucketContent;
		}
		
		return newDrawingGrid;
	}
	
	/**
	 * 
	 * This class keeps a reference to its static tile and a location that will
	 * need to be drawn relative to a camera
	 * @author Mitchell Thelen
	 *
	 */
	private final class CachedTile
	{
		// ===========================================================
		// Methods
		// ===========================================================
		
		public final int tileIndex;
		public final Vector2 position;
		
		// ===========================================================
		// Constructor
		// ===========================================================
		
		public CachedTile(int tileIndex, Vector2 position)
		{
			this.tileIndex = tileIndex;
			this.position = position;
		}
	}
	
	//TODO: level generator?
	public static final class Factory
	{
		private static int[][] currentGrid;
		private static TileWorld currentWorld;
		
		//TODO: nuke this sample
		public static TileWorld buildLevel1()
		{	
			//Create world
			currentWorld = new TileWorld(64, 64);
			
			//Create grid and drawing tiles
			currentGrid = createInitialCollisionGrid(64, 64);
			
			
			//god this is a mess: TODO: level editor
			addRow(3,
					null       , null       , null , null , null , null       , null       , null , null , null        , null ,        null , null , null,  null , null , null , null , null , BLOCK );
			addRow(2,
					null       , null       , null , null , null , null       , null       , null , null , null        , null ,        null , null, null,  BLOCK, null , null , null , null , BLOCK );
			addRow(1,
					null       , null       , null , null , null , RAMP_LEFT_1, RAMP_LEFT_2, BLOCK, BLOCK, RAMP_RIGHT_2, RAMP_RIGHT_1, null , BLOCK, BLOCK, BLOCK, BLOCK, null , null , null , BLOCK, null , null , null , null , null , null , null , BLOCK, null , null , null , null , null , null , BLOCK, BLOCK);
			addRow(0,
					RAMP_LEFT_1, RAMP_LEFT_2, BLOCK, BLOCK, BLOCK, BLOCK,       BLOCK,       BLOCK, BLOCK, BLOCK,        BLOCK,        BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK, BLOCK );


			//assign grid and return
			currentWorld.setTiles(currentGrid);
			return currentWorld;
		}
		
		private static int[][] createInitialCollisionGrid(int tilesAcross, int tilesUp) 
		{
			final int[][] collisionGrid = new int[tilesAcross][tilesUp];
			
			for(int x = 0; x < tilesAcross; x++)
				for(int y = 0; y < tilesUp; y++)
					collisionGrid[x][y] = TileConstants.NOT_SET;
			
			return collisionGrid;
		}

		@SuppressWarnings("unused") //might use this later...
		private static void addTile(int tileIndex, int posX, int posY)
		{
			assert posX >= 0 && posX < currentGrid.length : "setting tile outside of tile world's width";
			assert posY >= 0 && posY < currentGrid[posX].length : "setting tile outside of tile world's height";
			
			currentGrid[posX][posY] = tileIndex;
		}
		
		private static void addRow(int rowIndex, Tile... tiles)
		{
			assert rowIndex >= 0 && rowIndex < currentGrid.length : "setting number of tiles outside of tile world width";
			
			for(int i = 0; i < tiles.length; i++)
			{
				final Tile currentTile = tiles[i];
				
				if(currentTile == null)
					continue;
				
				currentGrid[i][rowIndex] = currentTile.index();
			}
		}
	}
}
